package org.apache.struts2.plugin.json.reflection;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.struts2.plugin.json.model.JSONObject;
import org.apache.struts2.plugin.json.model.JSONString;
import org.apache.struts2.plugin.json.model.JSONValue;
import org.apache.struts2.plugin.json.reflection.annotation.Parent;
import org.apache.struts2.plugin.json.reflection.annotation.Rename;
import org.apache.struts2.plugin.json.reflection.annotation.Unserializable;

/**
 * @author VERDOÏA Laurent <verdoialaurent@gmail.com>
 */
public class JSONObjectReflection {

    public static JSONValue reflection(Object o) throws JSONReflectionException {
        if (o instanceof Map) {
            return fromMap((Map<? extends Object, ? extends Object>) o);
        } else {
            return fromObject(o);
        }
    }

    public static JSONObject fromMap(Map<? extends Object, ? extends Object> o)
            throws JSONReflectionException {
        JSONObject j = new JSONObject();

        for (Entry<? extends Object, ? extends Object> entry : o.entrySet()) {
            JSONString k = JSONStringReflection.reflection(o);
            if (k == null) {
                throw new JSONReflectionException("The key must be string translatable");
            }
            JSONValue v = Reflection.reflection(o);
            j.add(k, v);
        }

        return j;
    }

    public static JSONObject fromObject(Object object) throws JSONReflectionException {
        Introspector.flushCaches();
        // Get all accessible methods
        BeanInfo beanInfo;

        try {
            Class<?> from = object.getClass();
            Class<?> to = from.getSuperclass();

            if (from.isAnnotationPresent(Parent.class)) {
                to = from.getAnnotation(Parent.class).value();
                if (to.equals(from)) {
                    throw new JSONReflectionException("Parent must not be the same class.");
                }
                to = to.getSuperclass();
            }

            beanInfo = Introspector.getBeanInfo(from, to);

        } catch (IntrospectionException ex) {
            throw new JSONReflectionException("Can't do introspection of object",
                    ex);
        }

        JSONObject j = new JSONObject();
        // Process each method
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            Method getMethod = propertyDescriptor.getReadMethod();
            if (getMethod == null || getMethod.isAnnotationPresent(Unserializable.class) == false) {
                continue;
            }
            JSONString name = fromObjectProcessMethodName(getMethod);
            JSONValue value = fromObjectProcessMethodValue(getMethod);
            j.add(name, value);
        }
        return j;
    }

    private static JSONString fromObjectProcessMethodName(Method method)
            throws JSONReflectionException {
        String name;
        if (method.isAnnotationPresent(Rename.class)) {
            name = method.getAnnotation(Rename.class).value();
            if (name.equals("")) {
                throw new JSONReflectionException("Name should not be a null string.");
            }
        } else {
            // Get the name without the 'get'
            name = method.getName().substring(3);
            name = Introspector.decapitalize(name);
        }
        return new JSONString(name);
    }

    private static JSONValue fromObjectProcessMethodValue(Method method)
            throws JSONReflectionException {
        try {
            Object data = method.invoke(method);
            return Reflection.reflection(data);
        } catch (Exception e) {
            throw new JSONReflectionException("Can't get value of method", e);
        }
    }
}
